diff options
Diffstat (limited to 'app/[lng]/evcp')
| -rw-r--r-- | app/[lng]/evcp/(evcp)/document-list-ship/page.tsx | 141 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx (renamed from app/[lng]/evcp/(evcp)/basic-contract-template/gtc/[id]/page.tsx) | 0 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/gtc/page.tsx (renamed from app/[lng]/evcp/(evcp)/basic-contract-template/gtc/page.tsx) | 0 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/information/page.tsx | 108 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/menu-list/page.tsx | 22 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx | 206 | ||||
| -rw-r--r-- | app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/types.ts | 4 |
7 files changed, 398 insertions, 83 deletions
diff --git a/app/[lng]/evcp/(evcp)/document-list-ship/page.tsx b/app/[lng]/evcp/(evcp)/document-list-ship/page.tsx new file mode 100644 index 00000000..321ce909 --- /dev/null +++ b/app/[lng]/evcp/(evcp)/document-list-ship/page.tsx @@ -0,0 +1,141 @@ +// page.tsx (간단한 Promise 생성과 로그인 처리) +import * as React from "react" +import { type SearchParams } from "@/types/table" +import { getValidFilters } from "@/lib/data-table" +import { Skeleton } from "@/components/ui/skeleton" +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" +import { Shell } from "@/components/shell" +import { searchParamsShipDocuCache } from "@/lib/vendor-document-list/validations" +import { getServerSession } from "next-auth" +import { authOptions } from "@/app/api/auth/[...nextauth]/route" +import Link from "next/link" +import { Button } from "@/components/ui/button" +import { LogIn } from "lucide-react" +import { getUserVendorDocumentStats, getUserVendorDocumentStatsAll, getUserVendorDocuments, getUserVendorDocumentsAll } from "@/lib/vendor-document-list/enhanced-document-service" +import { UserVendorDocumentDisplay } from "@/components/ship-vendor-document/user-vendor-document-table-container" +import { InformationButton } from "@/components/information/information-button" +import { UserVendorALLDocumentDisplay } from "@/components/ship-vendor-document-all/user-vendor-document-table-container" +interface IndexPageProps { + searchParams: Promise<SearchParams> +} + +export default async function IndexPage(props: IndexPageProps) { + const searchParams = await props.searchParams + const search = searchParamsShipDocuCache.parse(searchParams) + const validFilters = getValidFilters(search.filters) + + // Get session + const session = await getServerSession(authOptions) + + // Check if user is logged in + if (!session || !session.user) { + return ( + <Shell className="gap-6"> + <div className="flex items-center justify-between"> + <div> + <div className="flex items-center gap-2"> + <h2 className="text-2xl font-bold tracking-tight"> + 문서 관리 + </h2> + <InformationButton pagePath="partners/document-list-ship" /> + </div> + {/* <p className="text-muted-foreground"> + 소속 회사의 모든 도서/도면을 확인하고 관리합니다. + </p> */} + </div> + </div> + + <div className="flex flex-col items-center justify-center py-12 text-center"> + <div className="rounded-lg border border-dashed p-10 shadow-sm"> + <h3 className="mb-2 text-xl font-semibold">로그인이 필요합니다</h3> + <p className="mb-6 text-muted-foreground"> + 문서를 확인하려면 먼저 로그인하세요. + </p> + <Button size="lg" asChild> + <Link href="/partners"> + <LogIn className="mr-2 h-4 w-4" /> + 로그인하기 + </Link> + </Button> + </div> + </div> + </Shell> + ) + } + + // User is logged in, get user ID + const requesterId = session.user.id ? Number(session.user.id) : null + + if (!requesterId) { + return ( + <Shell className="gap-6"> + <div className="flex items-center justify-between"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + Document Management + </h2> + </div> + </div> + <div className="flex flex-col items-center justify-center py-12 text-center"> + <div className="rounded-lg border border-dashed p-10 shadow-sm"> + <h3 className="mb-2 text-xl font-semibold">계정 오류</h3> + <p className="mb-6 text-muted-foreground"> + 사용자 정보가 올바르게 설정되지 않았습니다. 관리자에게 문의하세요. + </p> + </div> + </div> + </Shell> + ) + } + + // 검색 파라미터 정리 + const searchInput = { + ...search, + filters: validFilters, + } + + // Promise 생성 (모든 데이터를 페이지에서 처리) + const documentsPromise = getUserVendorDocumentsAll(requesterId, searchInput) + const statsPromise = getUserVendorDocumentStatsAll(requesterId) + + // Promise.all로 감싸서 전달 + const allPromises = Promise.all([documentsPromise, statsPromise]) + const statsResult = await documentsPromise + + + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-between space-y-2"> + <div> + <h2 className="text-2xl font-bold tracking-tight"> + 조선 Document Management + </h2> + <p className="text-muted-foreground"> + + </p> + </div> + </div> + + <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> + {/* DateRangePicker can go here */} + </React.Suspense> + + <React.Suspense + fallback={ + <DataTableSkeleton + columnCount={8} + searchableColumnCount={1} + filterableColumnCount={3} + cellWidths={["10rem", "30rem", "15rem", "15rem", "15rem", "15rem", "8rem", "8rem"]} + shrinkZero + /> + } + > + <UserVendorALLDocumentDisplay + allPromises={allPromises} + /> + </React.Suspense> + </Shell> + ) +} + diff --git a/app/[lng]/evcp/(evcp)/basic-contract-template/gtc/[id]/page.tsx b/app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx index 0f783375..0f783375 100644 --- a/app/[lng]/evcp/(evcp)/basic-contract-template/gtc/[id]/page.tsx +++ b/app/[lng]/evcp/(evcp)/gtc/[id]/page.tsx diff --git a/app/[lng]/evcp/(evcp)/basic-contract-template/gtc/page.tsx b/app/[lng]/evcp/(evcp)/gtc/page.tsx index 33c504df..33c504df 100644 --- a/app/[lng]/evcp/(evcp)/basic-contract-template/gtc/page.tsx +++ b/app/[lng]/evcp/(evcp)/gtc/page.tsx diff --git a/app/[lng]/evcp/(evcp)/information/page.tsx b/app/[lng]/evcp/(evcp)/information/page.tsx index db383c32..c87471ae 100644 --- a/app/[lng]/evcp/(evcp)/information/page.tsx +++ b/app/[lng]/evcp/(evcp)/information/page.tsx @@ -1,58 +1,52 @@ -import * as React from "react"
-import type { Metadata } from "next"
-import { unstable_noStore as noStore } from "next/cache"
-
-import { Shell } from "@/components/shell"
-import { getInformationLists } from "@/lib/information/service"
-import { InformationClient } from "@/components/information/information-client"
-import { InformationButton } from "@/components/information/information-button"
-
-export const metadata: Metadata = {
- title: "인포메이션 관리",
- description: "페이지별 도움말 및 첨부파일을 관리합니다.",
-}
-
-interface InformationPageProps {
- params: Promise<{ lng: string }>
-}
-
-export default async function InformationPage({ params }: InformationPageProps) {
- noStore()
-
- const { lng } = await params
-
- // 초기 데이터 로딩
- const initialData = await getInformationLists({
- page: 1,
- perPage: 500,
- search: "",
- sort: [{ id: "createdAt", desc: true }],
- flags: [],
- filters: [],
- joinOperator: "and",
- pagePath: "",
- pageName: "",
- informationContent: "",
- isActive: null,
- from: "",
- to: "",
- })
-
- return (
- <Shell className="gap-2">
- <div className="flex items-center justify-between space-y-2">
- <div className="flex items-center justify-between space-y-2">
- <div>
- <div className="flex items-center gap-2">
- <h2 className="text-2xl font-bold tracking-tight">
- 인포메이션 관리
- </h2>
- <InformationButton pagePath="/evcp/information" />
- </div>
- </div>
- </div>
- </div>
- <InformationClient lng={lng} initialData={initialData?.data || []} />
- </Shell>
- )
+import * as React from "react" +import type { Metadata } from "next" +import { unstable_noStore as noStore } from "next/cache" + +import { Shell } from "@/components/shell" +import { getInformationLists } from "@/lib/information/service" +import { InformationClient } from "@/components/information/information-client" +import { InformationButton } from "@/components/information/information-button" +import { useTranslation } from "@/i18n" + +export const metadata: Metadata = { + title: "인포메이션 관리", + description: "페이지별 도움말 및 첨부파일을 관리합니다.", +} + +interface InformationPageProps { + params: Promise<{ lng: string }> +} + +export default async function InformationPage({ params }: InformationPageProps) { + noStore() + + const { lng } = await params + const { t } = await useTranslation(lng, 'menu') + + // 초기 데이터 로딩 (간단화) + const initialData = await getInformationLists() + + // 서버사이드에서 번역된 데이터 생성 + const translatedData = initialData?.data?.map(item => ({ + ...item, + translatedPageName: t(item.pageName) + })) || [] + + return ( + <Shell className="gap-2"> + <div className="flex items-center justify-between space-y-2"> + <div className="flex items-center justify-between space-y-2"> + <div> + <div className="flex items-center gap-2"> + <h2 className="text-2xl font-bold tracking-tight"> + 인포메이션 관리 + </h2> + <InformationButton pagePath="/evcp/information" /> + </div> + </div> + </div> + </div> + <InformationClient initialData={translatedData} /> + </Shell> + ) }
\ No newline at end of file diff --git a/app/[lng]/evcp/(evcp)/menu-list/page.tsx b/app/[lng]/evcp/(evcp)/menu-list/page.tsx index 6645127f..5a1f71a5 100644 --- a/app/[lng]/evcp/(evcp)/menu-list/page.tsx +++ b/app/[lng]/evcp/(evcp)/menu-list/page.tsx @@ -10,13 +10,31 @@ import { MenuListTable } from "@/lib/menu-list/table/menu-list-table"; import { Shell } from "@/components/shell" import * as React from "react" import { InformationButton } from "@/components/information/information-button"; -export default async function MenuListPage() { +import { useTranslation } from "@/i18n"; +interface MenuListPageProps { + params: Promise<{ lng: string }> +} + +export default async function MenuListPage({ params }: MenuListPageProps) { + const { lng } = await params + const { t } = await useTranslation(lng, 'menu') + // 초기 데이터 로드 const [menusResult, usersResult] = await Promise.all([ getMenuAssignments(), getActiveUsers() ]); + // 서버사이드에서 번역된 메뉴 데이터 생성 + const translatedMenus = menusResult.data?.map(menu => ({ + ...menu, + sectionTitle: menu.sectionTitle || "", + translatedMenuTitle: t(menu.menuTitle || ""), + translatedSectionTitle: t(menu.sectionTitle || ""), + translatedMenuGroup: menu.menuGroup ? t(menu.menuGroup) : null, + translatedMenuDescription: menu.menuDescription ? t(menu.menuDescription) : null + })) || []; + return ( <Shell className="gap-2"> <div className="flex items-center justify-between space-y-2"> @@ -60,7 +78,7 @@ export default async function MenuListPage() { <CardContent> <Suspense fallback={<div className="text-center py-8">로딩 중...</div>}> <MenuListTable - initialMenus={menusResult.data || []} + initialMenus={translatedMenus} initialUsers={usersResult.data || []} /> </Suspense> diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx index d00bfaa8..e92edc11 100644 --- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx +++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/basic-info-client.tsx @@ -18,6 +18,12 @@ import { toast } from "sonner"; import { VendorData, VendorFormData, VendorAttachment } from "./types"; import { updateVendorData } from "./actions"; import { noDataString } from "./constants"; +import { PQSimpleDialog } from "@/components/vendor-info/pq-simple-dialog"; +import { SiteVisitDetailDialog } from "@/lib/site-visit/site-visit-detail-dialog"; +import { DocumentStatusDialog } from "@/components/vendor-regular-registrations/document-status-dialog"; +import { AdditionalInfoDialog } from "@/components/vendor-regular-registrations/additional-info-dialog"; +import { getSiteVisitRequestsByVendorId } from "@/lib/site-visit/service"; +import { fetchVendorRegistrationStatus } from "@/lib/vendor-regular-registrations/service"; import { Table, TableBody, @@ -314,6 +320,16 @@ export default function BasicInfoClient({ }: BasicInfoClientProps) { const [editMode, setEditMode] = useState(false); const [isPending, startTransition] = useTransition(); + + // 다이얼로그 상태 + const [pqDialogOpen, setPqDialogOpen] = useState(false); + const [siteVisitDialogOpen, setSiteVisitDialogOpen] = useState(false); + const [contractDialogOpen, setContractDialogOpen] = useState(false); + const [additionalInfoDialogOpen, setAdditionalInfoDialogOpen] = useState(false); + + // 각 다이얼로그에 필요한 데이터 상태 + const [selectedSiteVisitRequest, setSelectedSiteVisitRequest] = useState<any>(null); + const [registrationData, setRegistrationData] = useState<any>(null); const [formData, setFormData] = useState<VendorFormData>({ vendorName: initialData?.vendorName || "", representativeName: initialData?.representativeName || "", @@ -326,6 +342,8 @@ export default function BasicInfoClient({ fax: initialData?.fax || "", email: initialData?.email || "", address: initialData?.address || "", + addressDetail: initialData?.addressDetail || "", + postalCode: initialData?.postalCode || "", businessSize: initialData?.businessSize || "", country: initialData?.country || "", website: initialData?.website || "", @@ -363,6 +381,8 @@ export default function BasicInfoClient({ fax: initialData?.fax || "", email: initialData?.email || "", address: initialData?.address || "", + addressDetail: initialData?.addressDetail || "", + postalCode: initialData?.postalCode || "", businessSize: initialData?.businessSize || "", country: initialData?.country || "", website: initialData?.website || "", @@ -387,6 +407,56 @@ export default function BasicInfoClient({ ); }; + // PQ 조회 핸들러 + const handlePQView = () => { + setPqDialogOpen(true); + }; + + // 실사 정보 조회 핸들러 + const handleSiteVisitView = async () => { + try { + const siteVisitRequests = await getSiteVisitRequestsByVendorId(parseInt(vendorId)); + if (siteVisitRequests.length === 0) { + toast.info("실사 정보가 없습니다."); + return; + } + setSelectedSiteVisitRequest(siteVisitRequests[0]); // 첫 번째 실사 정보 선택 + setSiteVisitDialogOpen(true); + } catch (error) { + console.error("실사 정보 조회 오류:", error); + toast.error("실사 정보를 불러오는데 실패했습니다."); + } + }; + + // 기본계약 현황 조회 핸들러 + const handleContractView = async () => { + try { + const result = await fetchVendorRegistrationStatus(parseInt(vendorId)); + if (!result.success || !result.data) { + toast.info("기본계약 정보가 없습니다."); + return; + } + + // 등록 데이터가 있는지 확인 + const registrationRecord = result.data; + if (!registrationRecord || !registrationRecord.documentSubmissions) { + toast.info("정규등록 정보가 없습니다."); + return; + } + + setRegistrationData(registrationRecord); + setContractDialogOpen(true); + } catch (error) { + console.error("기본계약 정보 조회 오류:", error); + toast.error("기본계약 정보를 불러오는데 실패했습니다."); + } + }; + + // 추가정보 조회 핸들러 + const handleAdditionalInfoView = () => { + setAdditionalInfoDialogOpen(true); + }; + if (!initialData) { return ( <div className="p-6 bg-background max-w-full"> @@ -459,12 +529,12 @@ export default function BasicInfoClient({ fieldKey="vendorName" onChange={(value) => updateField("vendorName", value)} /> - <InfoItem + {/* <InfoItem title="설립일" // 현재 필드 없고 linter error 나도 무시. createdAt은 데이터베이스 생성시점이므로 잘못된 필드. value={initialData.establishmentDate} type="readonly" - /> + /> */} <InfoItem title="대표전화" value={formData.phone} @@ -473,14 +543,14 @@ export default function BasicInfoClient({ fieldKey="phone" onChange={(value) => updateField("phone", value)} /> - <InfoItem + {/* <InfoItem title="팩스" value={formData.fax} isEditable={true} editMode={editMode} fieldKey="fax" onChange={(value) => updateField("fax", value)} - /> + /> */} <InfoItem title="업체유형" value={formData.businessType} @@ -489,7 +559,7 @@ export default function BasicInfoClient({ fieldKey="businessType" onChange={(value) => updateField("businessType", value)} /> - <InfoItem + {/* <InfoItem title="소개자료" value={`회사: ${ attachmentsByType.COMPANY_INTRO?.length || 0 @@ -498,7 +568,7 @@ export default function BasicInfoClient({ editMode={editMode} type="file-button" onFileButtonClick={() => handleFileManagement("소개자료")} - /> + /> */} <InfoItem title="정기평가 등급" value={ @@ -529,6 +599,22 @@ export default function BasicInfoClient({ onChange={(value) => updateField("address", value)} /> <InfoItem + title="상세주소" + value={formData.addressDetail} + isEditable={true} + editMode={editMode} + fieldKey="addressDetail" + onChange={(value) => updateField("addressDetail", value)} + /> + <InfoItem + title="우편번호" + value={formData.postalCode} + isEditable={true} + editMode={editMode} + fieldKey="postalCode" + onChange={(value) => updateField("postalCode", value)} + /> + <InfoItem title="E-mail" value={formData.email} isEditable={true} @@ -536,7 +622,7 @@ export default function BasicInfoClient({ fieldKey="email" onChange={(value) => updateField("email", value)} /> - <InfoItem + {/* <InfoItem title="사업유형" value={formData.businessType} isEditable={true} @@ -556,7 +642,7 @@ export default function BasicInfoClient({ ]} onChange={(value) => updateField("businessSize", value)} placeholder="기업규모를 선택하세요" - /> + /> */} <InfoItem title="사업자등록증" value={`${ @@ -586,11 +672,11 @@ export default function BasicInfoClient({ } isEditable={true} /> - <InfoItem + {/* <InfoItem title="그룹사" value={initialData.classificationInfo?.groupCompany || null} isEditable={true} - /> + /> */} <InfoItem title="국가" value={formData.country} @@ -602,13 +688,13 @@ export default function BasicInfoClient({ <InfoItem title="선호언어" value={ - initialData.classificationInfo?.preferredLanguage || null + initialData.classificationInfo?.preferredLanguage || "" } isEditable={true} /> <InfoItem title="산업유형" - value={initialData.classificationInfo?.industryType || null} + value={initialData.classificationInfo?.industryType || ""} isEditable={true} /> <InfoItem @@ -622,7 +708,7 @@ export default function BasicInfoClient({ <InfoItem title="당사거래비중" value={ - initialData.evaluationInfo?.companyTransactionRatio || null + initialData.evaluationInfo?.companyTransactionRatio || "" } type="readonly" /> @@ -633,7 +719,7 @@ export default function BasicInfoClient({ <Separator /> {/* 상세정보 */} - <InfoSection + {/* <InfoSection title="상세정보" column1={ <div className="space-y-2"> @@ -749,12 +835,12 @@ export default function BasicInfoClient({ </div> </div> } - /> + /> */} - <Separator /> + {/* <Separator /> */} {/* 매출정보 */} - <WideInfoSection + {/* <WideInfoSection title="매출정보" subtitle="(3개년)" noPadding={true} @@ -898,12 +984,12 @@ export default function BasicInfoClient({ </TableBody> </Table> } - /> + /> */} - <Separator /> + {/* <Separator /> */} {/* 실사정보 */} - <InfoSection + {/* <InfoSection title="실사정보" subtitle="(3년)" column1={ @@ -1004,12 +1090,12 @@ export default function BasicInfoClient({ </div> </div> } - /> + /> */} - <Separator /> + {/* <Separator /> */} {/* 계약정보 */} - <InfoSection + {/* <InfoSection title="계약정보" column1={ <div className="space-y-2"> @@ -1101,8 +1187,80 @@ export default function BasicInfoClient({ ))} </div> } - /> + /> */} + + {/* <Separator /> */} + + {/* 추가 조회 기능 버튼들 */} + <div className="border rounded-lg p-6"> + <div className="text-lg font-semibold mb-4">상세 정보 조회</div> + <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> + <Button + variant="outline" + onClick={handlePQView} + className="h-20 flex flex-col items-center justify-center space-y-2" + > + <div className="text-sm font-medium">PQ 조회</div> + <div className="text-xs text-muted-foreground">제출된 PQ 정보 확인</div> + </Button> + + <Button + variant="outline" + onClick={handleSiteVisitView} + className="h-20 flex flex-col items-center justify-center space-y-2" + > + <div className="text-sm font-medium">실사 정보</div> + <div className="text-xs text-muted-foreground">협력업체 방문실사 조회</div> + </Button> + + <Button + variant="outline" + onClick={handleContractView} + className="h-20 flex flex-col items-center justify-center space-y-2" + > + <div className="text-sm font-medium">정규업체 등록 현황</div> + <div className="text-xs text-muted-foreground">정규업체 등록 현황 보기</div> + </Button> + + <Button + variant="outline" + onClick={handleAdditionalInfoView} + className="h-20 flex flex-col items-center justify-center space-y-2" + > + <div className="text-sm font-medium">추가정보</div> + <div className="text-xs text-muted-foreground">업체 추가정보 조회</div> + </Button> + </div> + </div> </div> + + {/* 다이얼로그들 */} + <PQSimpleDialog + open={pqDialogOpen} + onOpenChange={setPqDialogOpen} + vendorId={vendorId} + /> + + <SiteVisitDetailDialog + isOpen={siteVisitDialogOpen} + onOpenChange={setSiteVisitDialogOpen} + selectedRequest={selectedSiteVisitRequest} + /> + + {registrationData && ( + <DocumentStatusDialog + open={contractDialogOpen} + onOpenChange={setContractDialogOpen} + registration={registrationData} + /> + )} + + <AdditionalInfoDialog + open={additionalInfoDialogOpen} + onOpenChange={setAdditionalInfoDialogOpen} + vendorId={parseInt(vendorId)} + readonly={true} + /> </div> ); } diff --git a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/types.ts b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/types.ts index 510ae361..ead3a44c 100644 --- a/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/types.ts +++ b/app/[lng]/evcp/(evcp)/vendors/[id]/info/basic/types.ts @@ -118,6 +118,8 @@ export interface VendorData { vendorCode: string; taxId: string; address: string; + addressDetail: string; + postalCode: string; businessSize: string; country: string; phone: string; @@ -163,6 +165,8 @@ export interface VendorFormData { representativeBirth: string; representativePhone: string; representativeEmail: string; + addressDetail: string; + postalCode: string; phone: string; fax: string; email: string; |
